/*
 * Copyright (C) 2015-2017 Alibaba Group Holding Limited
 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "fsl_flexspi.h"
#include "fsl_cache.h"

#include "flexspi_norflash.h"

#include "k_api.h"
#include "aos/kernel.h"


#define FLASH_READ_ALIGN_SIZE    8

flexspi_device_config_t deviceconfig = {
    .flexspiRootClk = 120000000,
    .flashSize = FLASH_SIZE,
    .CSIntervalUnit = kFLEXSPI_CsIntervalUnit1SckCycle,
    .CSInterval = 2,
    .CSHoldTime = 3,
    .CSSetupTime = 3,
    .dataValidTime = 0,
    .columnspace = 0,
    .enableWordAddress = 0,
    .AWRSeqIndex = 0,
    .AWRSeqNumber = 0,
    .ARDSeqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
    .ARDSeqNumber = 1,
    .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle,
    .AHBWriteWaitInterval = 0,
};

const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
        /* Normal read mode -SDR */
        [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x03, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
        [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 1] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Fast read mode - SDR */
        [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x0B, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
        [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST + 1] = FLEXSPI_LUT_SEQ(
            kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x08, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),

        /* Fast read quad mode - SDR */
        [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xEB, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 0x18),
        [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] = FLEXSPI_LUT_SEQ(
            kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x06, kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04),

        /* Read extend parameters */
        [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x81, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),

        /* Write Enable */
        [4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Erase Sector  */
        [4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x20, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),

        /* Page Program - single mode */
        [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x02, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
        [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE + 1] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Page Program - quad mode */
        [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x32, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18),
        [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Read ID */
        [4 * NOR_CMD_LUT_SEQ_IDX_READID] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xAB, kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x18),
        [4 * NOR_CMD_LUT_SEQ_IDX_READID + 1] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Enable Quad mode */
        [4 * NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x01, kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04),

        /* Enter QPI mode */
        [4 * NOR_CMD_LUT_SEQ_IDX_ENTERQPI] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x35, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Exit QPI mode */
        [4 * NOR_CMD_LUT_SEQ_IDX_EXITQPI] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_4PAD, 0xF5, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),

        /* Read status register */
        [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04),

        /* Erase whole chip */
        [4 * NOR_CMD_LUT_SEQ_IDX_ERASECHIP] =
            FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xC7, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0),
};

static inline void flexspi_clock_init(void)
{
    const clock_usb_pll_config_t g_ccmConfigUsbPll = {.loopDivider = 0U};
    CLOCK_InitUsb1Pll(&g_ccmConfigUsbPll);
    CLOCK_InitUsb1Pfd(kCLOCK_Pfd0, 24);   /* Set PLL3 PFD0 clock 360MHZ. */
    CLOCK_SetMux(kCLOCK_FlexspiMux, 0x3); /* Choose PLL3 PFD0 clock as flexspi source clock. */
    CLOCK_SetDiv(kCLOCK_FlexspiDiv, 2);   /* flexspi clock 120M. */
}

static status_t flexspi_nor_write_enable(FLEXSPI_Type *base, uint32_t baseAddr)
{
    flexspi_transfer_t flashXfer;
    status_t status;

    /* Write enable */
    flashXfer.deviceAddress = baseAddr;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Command;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE;

    status = FLEXSPI_TransferBlocking(base, &flashXfer);

    return status;
}

static status_t flexspi_nor_wait_bus_busy(FLEXSPI_Type *base)
{
    /* Wait status ready. */
    bool isBusy;
    uint32_t readValue;
    status_t status;
    flexspi_transfer_t flashXfer;

    flashXfer.deviceAddress = 0;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Read;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READSTATUSREG;
    flashXfer.data = &readValue;
    flashXfer.dataSize = 1;

    do
    {
        status = FLEXSPI_TransferBlocking(base, &flashXfer);

        if (status != kStatus_Success)
        {
            return status;
        }
        if (FLASH_BUSY_STATUS_POL)
        {
            if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET))
            {
                isBusy = true;
            }
            else
            {
                isBusy = false;
            }
        }
        else
        {
            if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET))
            {
                isBusy = false;
            }
            else
            {
                isBusy = true;
            }
        }

    } while (isBusy);

    return status;
}

status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base)
{
    flexspi_transfer_t flashXfer;
    status_t status;
    uint32_t writeValue = FLASH_QUAD_ENABLE;

    CPSR_ALLOC();
    RHINO_CPU_INTRPT_DISABLE();

    /* Write enable */
    status = flexspi_nor_write_enable(base, 0);

    if (status != kStatus_Success)
    {
        RHINO_CPU_INTRPT_ENABLE();
        return status;
    }

    /* Enable quad mode. */
    flashXfer.deviceAddress = 0;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Write;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG;
    flashXfer.data = &writeValue;
    flashXfer.dataSize = 1;

    status = FLEXSPI_TransferBlocking(base, &flashXfer);
    if (status != kStatus_Success)
    {
        RHINO_CPU_INTRPT_ENABLE();
        return status;
    }

    status = flexspi_nor_wait_bus_busy(base);
    RHINO_CPU_INTRPT_ENABLE();

    return status;
}

status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address)
{
    status_t status;
    flexspi_transfer_t flashXfer;

    CPSR_ALLOC();
    RHINO_CPU_INTRPT_DISABLE();
    /* Write enable */
    flashXfer.deviceAddress = address;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Command;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE;

    status = FLEXSPI_TransferBlocking(base, &flashXfer);

    if (status != kStatus_Success)
    {
        RHINO_CPU_INTRPT_ENABLE();
        return status;
    }

    flashXfer.deviceAddress = address;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Command;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ERASESECTOR;
    status = FLEXSPI_TransferBlocking(base, &flashXfer);


    if (status != kStatus_Success)
    {
        RHINO_CPU_INTRPT_ENABLE();
        return status;
    }

    status = flexspi_nor_wait_bus_busy(base);
    RHINO_CPU_INTRPT_ENABLE();

    return status;
}

status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src)
{
    status_t status;
    flexspi_transfer_t flashXfer;
    CPSR_ALLOC();

    RHINO_CPU_INTRPT_DISABLE();
    /* Write enable */
    status = flexspi_nor_write_enable(base, dstAddr);

    if (status != kStatus_Success)
    {
        RHINO_CPU_INTRPT_ENABLE();
        return status;
    }


    /* Prepare page program command */
    flashXfer.deviceAddress = dstAddr;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Write;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD;
    flashXfer.data = (uint32_t *)src;
    flashXfer.dataSize = FLASH_PAGE_SIZE;
    status = FLEXSPI_TransferBlocking(base, &flashXfer);

    if (status != kStatus_Success)
    {
        RHINO_CPU_INTRPT_ENABLE();
        return status;
    }

    status = flexspi_nor_wait_bus_busy(base);
    RHINO_CPU_INTRPT_ENABLE();
    return status;
}

status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId)
{
    uint32_t temp;
    flexspi_transfer_t flashXfer;
    flashXfer.deviceAddress = 0;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Read;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READID;
    flashXfer.data = &temp;
    flashXfer.dataSize = 1;

    status_t status = FLEXSPI_TransferBlocking(base, &flashXfer);

    *vendorId = temp;

    return status;
}

status_t flexspi_nor_erase_chip(FLEXSPI_Type *base)
{
    status_t status;
    flexspi_transfer_t flashXfer;

    /* Write enable */
    status = flexspi_nor_write_enable(base, 0);

    if (status != kStatus_Success)
    {
        return status;
    }

    flashXfer.deviceAddress = 0;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Command;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ERASECHIP;

    status = FLEXSPI_TransferBlocking(base, &flashXfer);

    if (status != kStatus_Success)
    {
        return status;
    }

    status = flexspi_nor_wait_bus_busy(base);

    return status;
}

static uint32_t nor_flash_get_text_base()
{
#if defined (__CC_ARM)
    extern unsigned int Image$$RW_m_config_text$$Base;
    return (uint32_t)&Image$$RW_m_config_text$$Base;
#elif defined (__ICCARM__)
    extern uint32_t __ICFEDIT_region_TEXT_start__;
    return (uint32_t)&__ICFEDIT_region_TEXT_start__;
#else 
    extern void* __FLASH_BASE;
    return (uint32_t)&__FLASH_BASE;
#endif
}

void flexspi_nor_flash_init(FLEXSPI_Type *base)
{
    flexspi_config_t config;

    if (nor_flash_get_text_base() != FLASH_START_ADDR) {
        return;
    }
    
    CPSR_ALLOC();
    RHINO_CPU_INTRPT_DISABLE();

    flexspi_clock_init();
    //SCB_DisableDCache();
    /*Get FLEXSPI default settings and configure the flexspi. */
    FLEXSPI_GetDefaultConfig(&config);

    /*Set AHB buffer size for reading data through AHB bus. */
    config.ahbConfig.enableAHBPrefetch = true;
    config.ahbConfig.enableAHBBufferable = true;
    config.ahbConfig.enableReadAddressOpt = true;
    config.ahbConfig.enableAHBCachable = true;

    config.rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad;
    FLEXSPI_Init(base, &config);

    /* Configure flash settings according to serial flash feature. */
    FLEXSPI_SetFlashConfig(base, &deviceconfig, kFLEXSPI_PortA1);

    /* Update LUT table. */
    FLEXSPI_UpdateLUT(base, 0, customLUT, CUSTOM_LUT_LENGTH);

    /* Do software reset. */
    FLEXSPI_SoftwareReset(base);

    SCB_InvalidateICache();
    RHINO_CPU_INTRPT_ENABLE();

}

status_t flexspi_nor_flash_align_read(uint32_t address, void *data, uint32_t size)
{
    uint32_t addroffset = 0;

    if (NULL == data || 0 == size || address < FLASH_START_ADDR) {
        return -1;
    }

    addroffset = address - FLASH_START_ADDR;

    flexspi_transfer_t flashXfer;
    flashXfer.deviceAddress = addroffset;
    flashXfer.port = kFLEXSPI_PortA1;
    flashXfer.cmdType = kFLEXSPI_Read;
    flashXfer.SeqNumber = 1;
    flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD;
    flashXfer.data = data;
    flashXfer.dataSize = size;

    CPSR_ALLOC();
    RHINO_CPU_INTRPT_DISABLE();
    status_t status = FLEXSPI_TransferBlocking(FLASH_FLEXSPI, &flashXfer);
    RHINO_CPU_INTRPT_ENABLE();

    return status;
}

status_t flexspi_nor_flash_read(uint32_t address, void *data, uint32_t size)
{
    uint32_t startaddr = 0;
    uint32_t lastpageaddr = 0;
    uint32_t pagecount = 0;
    uint32_t dataoffset = 0;
    uint32_t data_to_write = 0;
    uint32_t data_writed = 0;
    uint32_t i = 0;
    status_t ret = 0;
    uint8_t  uca_pagedata[FLASH_READ_ALIGN_SIZE] = {0};

    if (NULL == data || 0 == size) {
        return -1;
    }

    /*the first page address*/
    startaddr = (address/FLASH_READ_ALIGN_SIZE) * FLASH_READ_ALIGN_SIZE;

    /*the last page start address*/
    lastpageaddr = ((address + size - 1)/FLASH_READ_ALIGN_SIZE) * FLASH_READ_ALIGN_SIZE;

    /*write page count */
    pagecount = (lastpageaddr - startaddr) / FLASH_READ_ALIGN_SIZE + 1;

    for (i = 0; i < pagecount; i++) {

        memset(uca_pagedata, 0xff, FLASH_READ_ALIGN_SIZE);

        /*the first page*/
        if (i == 0) {
            dataoffset = address - startaddr;
            if(dataoffset + size < FLASH_READ_ALIGN_SIZE) {
                data_to_write = size;
            } else {
                data_to_write = FLASH_READ_ALIGN_SIZE - dataoffset;
            }
        } else if (i == (pagecount - 1)) {
            dataoffset = 0;
            data_to_write = address + size - lastpageaddr;
        } else {
            /*other page copy the data from the beginning*/
            dataoffset = 0;
            data_to_write = FLASH_READ_ALIGN_SIZE;
        }

        ret = flexspi_nor_flash_align_read(startaddr + i * FLASH_READ_ALIGN_SIZE, uca_pagedata, FLASH_READ_ALIGN_SIZE);
        if (ret != kStatus_Success) {
            printf("flexspi_nor_flash_write fail at line %d \r\n", __LINE__);
            return ret;
        }

        memcpy((uint8_t *)data + data_writed,uca_pagedata + dataoffset,  data_to_write);
        data_writed += data_to_write;

    }

    return ret;
}

status_t flexspi_nor_flash_write(uint32_t address, const void *data, uint32_t size)
{
    uint32_t startaddr = 0;
    uint32_t lastpageaddr = 0;
    uint32_t pagecount = 0;
    uint32_t dataoffset = 0;
    uint32_t data_to_write = 0;
    uint32_t data_writed = 0;
    uint32_t i = 0;
    status_t ret = 0;
    uint8_t  uca_pagedata[FLASH_PAGE_SIZE] = {0};

    if (NULL == data || 0 == size) {
        return -1;
    }

    /*the first page address*/
    startaddr = (address/FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE;

    /*the last page start address*/
    lastpageaddr = ((address + size - 1)/FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE;

    /*write page count */
    pagecount = (lastpageaddr - startaddr) / FLASH_PAGE_SIZE + 1;

    for (i = 0; i < pagecount; i++) {

        memset(uca_pagedata, 0xff, FLASH_PAGE_SIZE);

        /*the first page*/
        if (i == 0) {
            dataoffset = address - startaddr;
            if(dataoffset + size < FLASH_PAGE_SIZE) {
                data_to_write = size;
            } else {
                data_to_write = FLASH_PAGE_SIZE - dataoffset;
            }
        } else if (i == pagecount - 1) {
            dataoffset = 0;
            data_to_write = address + size - lastpageaddr;
        } else {
            /*other page copy the data from the beginning*/
            dataoffset = 0;
            data_to_write = FLASH_PAGE_SIZE;
        }

        memcpy(uca_pagedata + dataoffset, (uint8_t *)data + data_writed, data_to_write);
        data_writed += data_to_write;

        ret = flexspi_nor_flash_page_program(FLASH_FLEXSPI, startaddr - FLASH_START_ADDR + i * FLASH_PAGE_SIZE, uca_pagedata);
        if (ret != kStatus_Success) {
            printf("flexspi_nor_flash_write fail at line %d \r\n", __LINE__);
            return ret;
        }
    }

    return ret;
}


status_t flexspi_nor_flash_erase(uint32_t address, uint32_t size)
{
    uint32_t firstsector = 0;
    uint32_t lastsector = 0;
    uint32_t startaddr = 0;
    uint32_t lastaddr = 0;
    uint32_t i = 0;
    status_t ret = 0;

    startaddr = (address / SECTOR_SIZE) * SECTOR_SIZE;
    /*the last sector start address*/
    lastaddr = ((address + size - 1)/SECTOR_SIZE) * SECTOR_SIZE;

    firstsector = (startaddr - FLASH_START_ADDR) / SECTOR_SIZE;
    lastsector = (lastaddr - FLASH_START_ADDR) / SECTOR_SIZE;

    for (i = firstsector; i < lastsector + 1; i++) {
        ret = flexspi_nor_flash_erase_sector(FLASH_FLEXSPI, i * SECTOR_SIZE);
        if (ret != kStatus_Success) {
            printf("flexspi_nor_flash_erase fail at line %d \r\n", __LINE__);
            return -1;
        }
    }

    return ret;
}


